// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_COMPILER_FFI_NATIVE_TYPE_H_
#define RUNTIME_VM_COMPILER_FFI_NATIVE_TYPE_H_

#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/allocation.h"
#include "vm/constants_base.h"
#include "vm/growable_array.h"

#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/ffi/range.h"
#endif
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
#include "vm/compiler/backend/locations.h"
#endif
#if !defined(FFI_UNIT_TESTS)
#include "vm/object.h"
#endif

#if !defined(FFI_UNIT_TESTS)
#define UNREACHABLE_THIS() FATAL("Unreachable code with: %s", ToCString())
#else
// No Zone-less ToCString() in tests.
#define UNREACHABLE_THIS() UNREACHABLE()
#endif

namespace dart {

class BaseTextBuffer;

namespace compiler {

namespace ffi {

class NativePrimitiveType;
class NativeArrayType;
class NativeCompoundType;
class NativeStructType;

// NativeTypes are the types used in calling convention specifications:
// integers, floats, and compounds.
//
// NativeTypes exclude C types which are not discussed in calling conventions:
// pointer types (they are lowered to integers).
//
// The NativeTypes are a partially overlapping with unboxed Representations.
// NativeTypes do not have Dart representations such as the following:
// * tagged
// * untagged
//
// Instead, NativeTypes support representations not supported in Dart's unboxed
// Representations, such as:
// * Primitive types (https://en.cppreference.com/w/cpp/language/types):
//   * int8_t
//   * int16_t
//   * uint8_t
//   * uint16t
//   * void
// * Compound types (https://en.cppreference.com/w/cpp/language/type):
//   * Struct
//   * Union
class NativeType : public ZoneAllocated {
 public:
#if !defined(FFI_UNIT_TESTS)
  static const NativeType* FromAbstractType(Zone* zone,
                                            const AbstractType& type,
                                            const char** error);
#endif
  static const NativeType& FromTypedDataClassId(Zone* zone, classid_t class_id);

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
  static NativePrimitiveType& FromRepresentation(Zone* zone,
                                                 Representation rep);
#endif  // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)

  virtual bool IsPrimitive() const { return false; }
  const NativePrimitiveType& AsPrimitive() const;
  virtual bool IsArray() const { return false; }
  const NativeArrayType& AsArray() const;
  virtual bool IsCompound() const { return false; }
  const NativeCompoundType& AsCompound() const;
  virtual bool IsStruct() const { return false; }
  const NativeStructType& AsStruct() const;

  virtual bool IsInt() const { return false; }
  virtual bool IsFloat() const { return false; }
  virtual bool IsVoid() const { return false; }

  virtual bool IsSigned() const { return false; }

  // The size in bytes of this representation.
  //
  // Does not take into account padding required if repeating.
  virtual intptr_t SizeInBytes() const = 0;

  // The alignment in bytes of this representation on the stack.
  virtual intptr_t AlignmentInBytesStack(bool is_vararg = false) const = 0;

  // The alignment in bytes of this representation as member of a composite.
  virtual intptr_t AlignmentInBytesField() const = 0;

#if !defined(DART_PRECOMPILED_RUNTIME)
  // Returns true iff a range within this type contains only floats.
  //
  // Useful for determining whether struct is passed in FP registers on x64.
  virtual bool ContainsOnlyFloats(Range range) const = 0;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  // True iff any members are misaligned recursively due to packing.
  virtual bool ContainsUnalignedMembers(intptr_t offset = 0) const = 0;

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
  // NativeTypes which are available as unboxed Representations.
  virtual bool IsExpressibleAsRepresentation() const { return false; }

  // Unboxed Representation if it exists.
  virtual Representation AsRepresentation() const { UNREACHABLE_THIS(); }

  // Unboxed Representation, over approximates if needed.
  Representation AsRepresentationOverApprox(Zone* zone_) const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)

  virtual bool Equals(const NativeType& other) const { UNREACHABLE_THIS(); }

  // Split representation in two.
  virtual NativeType& Split(Zone* zone, intptr_t index) const {
    UNREACHABLE_THIS();
  }

  // If this is a 8 or 16 bit int, returns a 32 bit container.
  // Otherwise, return original representation.
  const NativeType& WidenTo4Bytes(Zone* zone) const;
  // If this is a 8, 16 or 32 bit int, returns a 64 bit container.
  // Otherwise, return original representation.
  const NativeType& WidenTo8Bytes(Zone* zone) const;
  const NativeType& Extend(Zone* zone, ExtensionStrategy extension) const {
    switch (extension) {
      case kNotExtended:
        return *this;
      case kExtendedTo4:
        return WidenTo4Bytes(zone);
      case kExtendedTo8:
        return WidenTo8Bytes(zone);
      default:
        UNREACHABLE();
    }
  }

  virtual void PrintTo(BaseTextBuffer* f,
                       bool multi_line = false,
                       bool verbose = true) const;
  const char* ToCString(Zone* zone,
                        bool multi_line = false,
                        bool verbose = true) const;
#if !defined(FFI_UNIT_TESTS)
  const char* ToCString() const;
#endif

  virtual intptr_t NumPrimitiveMembersRecursive() const = 0;
  virtual const NativePrimitiveType& FirstPrimitiveMember() const = 0;

  // Returns the number of primitive members when this aggregate is flattened
  // out, and sets the out-parameters to the first two such primitive members.
  virtual intptr_t PrimitivePairMembers(
      const NativePrimitiveType** first,
      const NativePrimitiveType** second,
      intptr_t offset_in_members = 0) const = 0;

  virtual ~NativeType() {}

 protected:
  NativeType() {}
};

enum PrimitiveType {
  kInt8,
  kUint8,
  kInt16,
  kUint16,
  kInt32,
  kUint32,
  kInt64,
  kUint64,
  kFloat,
  kDouble,
  kHalfDouble,  // When doubles are split over two 32 bit locations.
  kInt24,
  kUint24,
  kInt40,
  kUint40,
  kInt48,
  kUint48,
  kInt56,
  kUint56,
  kVoid,
  // TODO(37470): Add packed data structures.
};

// Used for the type representation of kUntagged (Pointer, where the untagged
// pointer in the data field is extracted by IL) and kTagged (Handle, turned
// into untagged pointers to the stack during the FfiCall) values. Should
// be kept consistent with the Representation kUnboxedAddress.
#if defined(TARGET_ARCH_IS_32_BIT)
constexpr PrimitiveType kAddress = kUint32;
#else
constexpr PrimitiveType kAddress = kInt64;
#endif

PrimitiveType PrimitiveTypeFromSizeInBytes(intptr_t size);

// Represents a primitive native type.
//
// These are called object types in the C standard (ISO/IEC 9899:2011) and
// fundamental types in C++ (https://en.cppreference.com/w/cpp/language/types)
// but more commonly these are called primitive types
// (https://en.wikipedia.org/wiki/Primitive_data_type).
class NativePrimitiveType : public NativeType {
 public:
  explicit NativePrimitiveType(PrimitiveType rep) : representation_(rep) {}

  PrimitiveType representation() const { return representation_; }

  virtual bool IsPrimitive() const { return true; }

  virtual bool IsInt() const;
  virtual bool IsFloat() const;
  virtual bool IsVoid() const;

  virtual bool IsSigned() const;

  virtual intptr_t SizeInBytes() const;
  virtual intptr_t AlignmentInBytesStack(bool is_vararg = false) const;
  virtual intptr_t AlignmentInBytesField() const;

#if !defined(DART_PRECOMPILED_RUNTIME)
  virtual bool ContainsOnlyFloats(Range range) const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  virtual bool ContainsUnalignedMembers(intptr_t offset = 0) const;

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
  virtual bool IsExpressibleAsRepresentation() const;
  virtual Representation AsRepresentation() const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)

  virtual bool Equals(const NativeType& other) const;
  virtual NativePrimitiveType& Split(Zone* zone, intptr_t part) const;

  virtual void PrintTo(BaseTextBuffer* f,
                       bool multi_line = false,
                       bool verbose = true) const;

  virtual intptr_t NumPrimitiveMembersRecursive() const;
  virtual const NativePrimitiveType& FirstPrimitiveMember() const;
  virtual intptr_t PrimitivePairMembers(const NativePrimitiveType** first,
                                        const NativePrimitiveType** second,
                                        intptr_t offset_in_members = 0) const;

  virtual ~NativePrimitiveType() {}

 private:
  const PrimitiveType representation_;
};

// Fixed-length arrays.
class NativeArrayType : public NativeType {
 public:
  NativeArrayType(const NativeType& element_type, intptr_t length)
      : element_type_(element_type), length_(length) {
    ASSERT(!element_type.IsArray());
    ASSERT(length > 0);
  }

  const NativeType& element_type() const { return element_type_; }
  intptr_t length() const { return length_; }

  virtual bool IsArray() const { return true; }

  virtual intptr_t SizeInBytes() const {
    return element_type_.SizeInBytes() * length_;
  }
  virtual intptr_t AlignmentInBytesField() const {
    return element_type_.AlignmentInBytesField();
  }
  virtual intptr_t AlignmentInBytesStack(bool is_vararg = false) const {
    return element_type_.AlignmentInBytesStack();
  }

#if !defined(DART_PRECOMPILED_RUNTIME)
  virtual bool ContainsOnlyFloats(Range range) const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  virtual bool ContainsUnalignedMembers(intptr_t offset = 0) const;

  virtual bool Equals(const NativeType& other) const;

  virtual void PrintTo(BaseTextBuffer* f,
                       bool multi_line = false,
                       bool verbose = true) const;

  virtual intptr_t NumPrimitiveMembersRecursive() const;
  virtual const NativePrimitiveType& FirstPrimitiveMember() const;
  virtual intptr_t PrimitivePairMembers(const NativePrimitiveType** first,
                                        const NativePrimitiveType** second,
                                        intptr_t offset_in_members = 0) const;

 private:
  const NativeType& element_type_;
  const intptr_t length_;
};

using NativeTypes = ZoneGrowableArray<const NativeType*>;

// Compound native types: native arrays and native structs.
class NativeCompoundType : public NativeType {
 public:
  const NativeTypes& members() const { return members_; }

  virtual bool IsCompound() const { return true; }

  virtual intptr_t SizeInBytes() const { return size_; }
  virtual intptr_t AlignmentInBytesField() const { return alignment_field_; }
  virtual intptr_t AlignmentInBytesStack(bool is_vararg = false) const {
    return alignment_stack_;
  }

  virtual bool Equals(const NativeType& other) const;

  virtual void PrintTo(BaseTextBuffer* f,
                       bool multi_line = false,
                       bool verbose = true) const;

#if !defined(DART_PRECOMPILED_RUNTIME)
  virtual bool ContainsOnlyFloats(Range range) const = 0;

  // Returns how many word-sized chunks _only_ contain floats.
  //
  // Useful for determining whether struct is passed in FP registers on x64.
  intptr_t NumberOfWordSizeChunksOnlyFloat() const;

  // Returns how many word-sized chunks do not _only_ contain floats.
  //
  // Useful for determining whether struct is passed in FP registers on x64.
  intptr_t NumberOfWordSizeChunksNotOnlyFloat() const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  virtual bool ContainsUnalignedMembers(intptr_t offset = 0) const = 0;

  // Whether this type has only same-size floating point members.
  //
  // Useful for determining whether struct is passed in FP registers in hardfp
  // and arm64.
  bool ContainsHomogeneousFloats() const;

  virtual intptr_t NumPrimitiveMembersRecursive() const = 0;
  virtual const NativePrimitiveType& FirstPrimitiveMember() const;
  virtual intptr_t PrimitivePairMembers(const NativePrimitiveType** first,
                                        const NativePrimitiveType** second,
                                        intptr_t offset_in_members = 0) const;

 protected:
  NativeCompoundType(const NativeTypes& members,
                     intptr_t size,
                     intptr_t alignment_field,
                     intptr_t alignment_stack)
      : members_(members),
        size_(size),
        alignment_field_(alignment_field),
        alignment_stack_(alignment_stack) {}

  const NativeTypes& members_;
  const intptr_t size_;
  const intptr_t alignment_field_;
  const intptr_t alignment_stack_;

  virtual void PrintCompoundType(BaseTextBuffer* f) const = 0;
  virtual void PrintMemberOffset(BaseTextBuffer* f,
                                 intptr_t member_index) const {}
};

// Native structs.
class NativeStructType : public NativeCompoundType {
 public:
  static NativeStructType& FromNativeTypes(Zone* zone,
                                           const NativeTypes& members,
                                           intptr_t member_packing = kMaxInt32);

  const ZoneGrowableArray<intptr_t>& member_offsets() const {
    return member_offsets_;
  }

  virtual bool IsStruct() const { return true; }

  virtual intptr_t AlignmentInBytesStack(bool is_vararg = false) const {
    if (is_vararg) {
      return alignment_stack_vararg_;
    }
    return alignment_stack_;
  }

#if !defined(DART_PRECOMPILED_RUNTIME)
  virtual bool ContainsOnlyFloats(Range range) const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  virtual bool ContainsUnalignedMembers(intptr_t offset = 0) const;

  virtual intptr_t NumPrimitiveMembersRecursive() const;

 protected:
  virtual void PrintCompoundType(BaseTextBuffer* f) const;
  virtual void PrintMemberOffset(BaseTextBuffer* f,
                                 intptr_t member_index) const;

 private:
  NativeStructType(const NativeTypes& members,
                   const ZoneGrowableArray<intptr_t>& member_offsets,
                   intptr_t size,
                   intptr_t alignment_field,
                   intptr_t alignment_stack,
                   intptr_t alignment_stack_vararg)
      : NativeCompoundType(members, size, alignment_field, alignment_stack),
        alignment_stack_vararg_(alignment_stack_vararg),
        member_offsets_(member_offsets) {}

  const intptr_t alignment_stack_vararg_;
  const ZoneGrowableArray<intptr_t>& member_offsets_;
};

// Native unions.
class NativeUnionType : public NativeCompoundType {
 public:
  static NativeUnionType& FromNativeTypes(Zone* zone,
                                          const NativeTypes& members);

#if !defined(DART_PRECOMPILED_RUNTIME)
  virtual bool ContainsOnlyFloats(Range range) const;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  virtual bool ContainsUnalignedMembers(intptr_t offset = 0) const;

  virtual intptr_t NumPrimitiveMembersRecursive() const;

 protected:
  virtual void PrintCompoundType(BaseTextBuffer* f) const;

 private:
  NativeUnionType(const NativeTypes& members,
                  intptr_t size,
                  intptr_t alignment_field,
                  intptr_t alignment_stack)
      : NativeCompoundType(members, size, alignment_field, alignment_stack) {}
};

class NativeFunctionType : public ZoneAllocated {
 public:
  NativeFunctionType(const NativeTypes& argument_types,
                     const NativeType& return_type,
                     intptr_t variadic_arguments_index = kNoVariadicArguments)
      : argument_types_(argument_types),
        return_type_(return_type),
        variadic_arguments_index_(variadic_arguments_index) {}

#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(FFI_UNIT_TESTS)
  static const NativeFunctionType* FromRepresentations(
      Zone* zone,
      Representation return_representation,
      const ZoneGrowableArray<Representation>& argument_representations);
#endif

  const NativeTypes& argument_types() const { return argument_types_; }
  const NativeType& return_type() const { return return_type_; }
  intptr_t variadic_arguments_index() const {
    return variadic_arguments_index_;
  }

  void PrintTo(BaseTextBuffer* f) const;
  const char* ToCString(Zone* zone) const;
#if !defined(FFI_UNIT_TESTS)
  const char* ToCString() const;
#endif

  static constexpr intptr_t kNoVariadicArguments = INTPTR_MAX;

 private:
  const NativeTypes& argument_types_;
  const NativeType& return_type_;
  // If no variadic arguments, then kNoVariadicArguments.
  const intptr_t variadic_arguments_index_;
};

}  // namespace ffi

}  // namespace compiler

}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_FFI_NATIVE_TYPE_H_
